/*******************************************************************************
 * Copyright 2010 Simon Mieth
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package org.kabeja.svg.action;

import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.swing.AbstractAction;
import javax.swing.ImageIcon;

import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.kabeja.processing.BoundsFilter;
import org.kabeja.svg.SVGConstants;
import org.kabeja.svg.SVGUtils;
import org.kabeja.ui.PropertiesEditor;
import org.kabeja.ui.PropertiesListener;
import org.w3c.dom.Element;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.events.MouseEvent;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGMatrix;

import de.miethxml.toolkit.ui.UIUtils;


public class SelectionAction extends AbstractAction implements SVGDocumentAction,
    EventListener, PropertiesEditor, ItemListener, CanvasUpdateRunnable,
    GroupActionUnSelector {
    protected boolean selecting = false;
    protected boolean dragging = false;
    protected Element selectionRectangle;
    protected float startX = 0;
    protected float startY = 0;
    protected SVGMatrix matrix;
    protected Map properties = new HashMap();
    protected List listeners = new ArrayList();
    float lastX;
    float lastY;
    protected boolean process = false;
    protected boolean state = false;
    protected CanvasUpdateManager updateManager;

    public SelectionAction() {
        super("Selection",
            new ImageIcon(UIUtils.resourceToBytes(SelectionAction.class,
                    "/icons/cut_edit.gif")));
        putValue(SHORT_DESCRIPTION, Messages.getString("editor.action.cut.area"));
    }

    public void handleEvent(Event evt) {
        if (this.state) {
            MouseEvent event = (MouseEvent) evt;

            if (evt.getType().equals(SVGConstants.SVGEVENT_CLICK) && selecting) {
                selecting = false;

                removeSelectionRectangle();

                // evt.stopPropagation();
            } else if (evt.getType().equals(SVGConstants.SVGEVENT_MOUSEDOWN) &&
                    !selecting) {
                // setup the start and end positions
                this.matrix = ((SVGDocument) this.selectionRectangle
                               .getOwnerDocument()).getRootElement()
                               .getScreenCTM().inverse();
                this.startX = (matrix.getA() * event.getClientX()) +
                    (matrix.getC() * event.getClientY()) + matrix.getE();
                this.startY = (matrix.getB() * event.getClientX()) +
                    (matrix.getD() * event.getClientY()) + matrix.getF();

                this.changePosition(this.startX, this.startY,
                    event.getClientX(), event.getClientX());
                this.selectionRectangle.setAttributeNS(null, "visibility",
                    "visible");

                selecting = true;

                // evt.stopPropagation();
            } else if (evt.getType().equals(SVGConstants.SVGEVENT_MOUSEMOVE) &&
                    selecting) {
                // evt.getTarget()
                // System.out.println("moving with button");
                if ((Math.abs(lastX - event.getScreenX()) > 1) ||
                        (Math.abs(lastY - event.getScreenY()) > 1)) {
                    this.changePosition(this.startX, this.startY,
                        event.getClientX(), event.getClientY());
                    lastX = event.getScreenX();
                    lastY = event.getScreenY();
                }
            } else if (evt.getType().equals(SVGConstants.SVGEVENT_MOUSEUP) &&
                    selecting) {
                selecting = false;
                process = true;
                this.changePosition(this.startX, this.startY,
                    event.getClientX(), event.getClientY());
                this.firePropertiesUpdated();
            }
        }
    }

    protected void removeSelectionRectangle() {
        this.selectionRectangle.setAttributeNS(null, "visibility", "hidden");
        this.process = false;
        this.firePropertiesUpdated();
    }

    protected void changePosition(float x, float y, float endX, float endY) {
        this.selectionRectangle.setAttributeNS(null, "y", "" + y);

        float width = (float) (((matrix.getA() * endX) +
            (matrix.getC() * endY) + matrix.getE()) - x);
        float height = (float) (((matrix.getB() * endX) +
            (matrix.getD() * endY) + matrix.getF()) - y);

        if (width < 0) {
            this.selectionRectangle.setAttributeNS(null, "x",
                "" + SVGUtils.formatNumberAttribute(x + width));
            this.selectionRectangle.setAttributeNS(null, "width",
                "" + SVGUtils.formatNumberAttribute(Math.abs(width)));
        } else {
            this.selectionRectangle.setAttributeNS(null, "x",
                "" + SVGUtils.formatNumberAttribute(x));
            this.selectionRectangle.setAttributeNS(null, "width",
                "" + SVGUtils.formatNumberAttribute(width));
        }

        if (height < 0) {
            this.selectionRectangle.setAttributeNS(null, "y",
                "" + SVGUtils.formatNumberAttribute(y + height));
            this.selectionRectangle.setAttributeNS(null, "height",
                "" + SVGUtils.formatNumberAttribute(Math.abs(height)));
        } else {
            this.selectionRectangle.setAttributeNS(null, "y", "" + y);
            this.selectionRectangle.setAttributeNS(null, "height",
                "" + SVGUtils.formatNumberAttribute(height));
        }
    }

    public void setDocument(SVGDocument doc) {
        Element el = doc.getElementById("draft");
        String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI;

        // we parse the viewBox
        String s = doc.getDocumentElement().getAttribute("viewBox");

        String[] data = s.split("\\s+");
        Element rectangle = doc.createElementNS(svgNS, "rect");

        // we create a invisible rectangle for the capturing the mouse events
        double x = Double.parseDouble(data[0]);

        double y = Double.parseDouble(data[1]);

        double width = Double.parseDouble(data[2]);

        double height = Double.parseDouble(data[3]);

        rectangle.setAttributeNS(null, "x",
            SVGUtils.formatNumberAttribute((x - (width / 2))));

        rectangle.setAttributeNS(null, "y",
            SVGUtils.formatNumberAttribute(y - (height / 2)));

        rectangle.setAttributeNS(null, "width",
            SVGUtils.formatNumberAttribute(width * 2));
        rectangle.setAttributeNS(null, "height",
            SVGUtils.formatNumberAttribute(height * 2));
        rectangle.setAttributeNS(null, "style", "fill:blue;opacity:0.0;");

        EventTarget t = (EventTarget) rectangle;
        t.addEventListener("click", this, false);
        t.addEventListener("mousedown", this, false);
        t.addEventListener("mouseup", this, false);
        t.addEventListener("mousemove", this, false);

        this.selectionRectangle = doc.createElementNS(svgNS, "rect");
        this.selectionRectangle.setAttributeNS(null, "x", data[0]);
        this.selectionRectangle.setAttributeNS(null, "y", data[1]);
        this.selectionRectangle.setAttributeNS(null, "width", data[2]);
        this.selectionRectangle.setAttributeNS(null, "height", data[3]);
        this.selectionRectangle.setAttributeNS(null, "style",
            "fill:blue;fill-opacity:0.10;stroke:black;stroke-width:0.10%;stroke-opacity:1.0");
        this.selectionRectangle.setAttributeNS(null, "visibility", "hidden");
        //doc.getDocumentElement().appendChild(this.selectionRectangle);
        //doc.getDocumentElement().appendChild(rectangle);
        doc.getDocumentElement()
           .insertBefore(rectangle, doc.getDocumentElement().getFirstChild());
        doc.getDocumentElement().insertBefore(this.selectionRectangle, rectangle);
    }

    public void addPropertiesListener(PropertiesListener listener) {
        this.listeners.add(listener);
    }

    public Map getProperties() {
        return this.properties;
    }

    public void removePropertiesListener(PropertiesListener listener) {
        this.listeners.remove(listener);
    }

    public void setProperties(Map properties) {
    }

    protected void firePropertiesUpdated() {
        // update the properties data
        double height = Double.parseDouble(this.selectionRectangle.getAttribute(
                    "height"));
        // TODO Auto-generated method stub
        this.properties.put(BoundsFilter.PROPERTY_X,
            this.selectionRectangle.getAttribute("x"));
        this.properties.put(BoundsFilter.PROPERTY_Y,
            "" +
            (((-1) * Double.parseDouble(this.selectionRectangle.getAttribute(
                    "y"))) - height));
        this.properties.put(BoundsFilter.PROPERTY_WIDTH,
            this.selectionRectangle.getAttribute("width"));
        this.properties.put(BoundsFilter.PROPERTY_HEIGHT, "" + height);
        this.properties.put(BoundsFilter.PROPERTY_PROCESS,
            Boolean.toString(process));

        Iterator i = this.listeners.iterator();

        while (i.hasNext()) {
            PropertiesListener listener = (PropertiesListener) i.next();
            listener.propertiesChanged(this.properties);
        }
    }

    public void actionPerformed(ActionEvent e) {
    }

    public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
            this.state = true;
        } else {
            this.state = false;
            // this should run in the UpdateManager RunnableQueue
            this.removeSelectionRectangle();
        }
    }

    public void setCanvasUpdateManager(CanvasUpdateManager manager) {
        this.updateManager = manager;
    }
}
